import { MethodSignature, Scene, SceneConfig } from '../src';
import { BlockNode, ClassNode, FileNode, MethodNode, Node, StmtNode } from './Nodes';
import { CallGraph } from '../src/callgraph/model/CallGraph';
import { CallGraphBuilder } from '../src/callgraph/model/builder/CallGraphBuilder';

export class Analyzer {
    private readonly scene: Scene;
    private readonly nodes: Node[];
    private readonly callGraph: CallGraph;

    public constructor(directory: string) {
        const config = new SceneConfig();
        config.buildFromProjectDir(directory);
        this.scene = new Scene();
        this.scene.buildSceneFromProjectDir(config);
        this.scene.inferTypes();
        this.nodes = [];
        this.callGraph = new CallGraph(this.scene);
    }

    public generateNodesAndEdges(): void {
        for (const arkFile of this.scene.getFiles()) {
            const fileNode = new FileNode(arkFile);
            this.nodes.push(fileNode);
            for (const arkClass of arkFile.getClasses()) {
                const classNode = new ClassNode(arkClass);
                fileNode.addChild(classNode);
                for (const arkMethod of arkClass.getMethods()) {
                    if (arkMethod.getName() == '_DEFAULT_ARK_METHOD') {
                        continue;
                    }
                    const methodNode = new MethodNode(arkMethod);
                    classNode.addChild(methodNode);
                    const body = arkMethod.getBody();
                    if (body !== undefined) {
                        const blocks = [...body.getCfg().getBlocks()];
                        for (const block of blocks) {
                            const blockNode = new BlockNode(block);
                            methodNode.addChild(blockNode);
                            for (const stmt of block.getStmts()) {
                                const stmtNode = new StmtNode(stmt);
                                blockNode.addChild(stmtNode);
                            }
                        }
                    }
                }
            }
        }
    }

    public generateCallGraph(dumpPath?: string): void {
        const entryPoints: MethodSignature[] = [];
        for (const method of this.scene.getMethods()) {
            entryPoints.push(method.getSignature());
        }
        entryPoints.push(...this.scene.getFiles()
            .flatMap(arkFile => arkFile.getClasses())
            .flatMap(arkClass => arkClass.getMethods())
            .map(arkMethod => arkMethod.getSignature())
        );
        const callGraphBuilder = new CallGraphBuilder(this.callGraph, this.scene);
        callGraphBuilder.buildClassHierarchyCallGraph(entryPoints);
        callGraphBuilder.buildRapidTypeCallGraph(entryPoints);
        if (dumpPath !== undefined && dumpPath !== null) {
            this.callGraph.dump(dumpPath);
        }
    }

    public dumpCallGraph(path: string) {
        this.callGraph.dump(path);
    }

    public getNodes(): Node[] {
        return this.nodes;
    }

    public getScene(): Scene {
        return this.scene;
    }
}
